package com.tbit.uqbike.service.business.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Joiner;
import com.tbit.common.config.OssDefaultConfig;
import com.tbit.uqbike.constant.ControlTypeConstant;
import com.tbit.uqbike.constant.GatewayProtocol;
import com.tbit.common.constant.OrderLogConstant;
import com.tbit.uqbike.constant.RabbitMQData;
import com.tbit.uqbike.manager.exception.BaseException;
import com.tbit.uqbike.object.business.Point;
import com.tbit.uqbike.object.pojo.*;
import com.tbit.uqbike.object.pojo.dto.TaskDTO;
import com.tbit.uqbike.object.pojo.dto.VoiceTaskDTO;
import com.tbit.uqbike.object.pojo.vo.UpgradeVO;
import com.tbit.uqbike.object.pojo.vo.VoiceTaskVO;
import com.tbit.uqbike.object.value.LoginInfo;
import com.tbit.common.entity.view.PageResult;
import com.tbit.common.entity.view.Result;
import com.tbit.uqbike.service.base.MQProducer;
import com.tbit.uqbike.service.business.VoiceTaskService;
import com.tbit.uqbike.webmanager.controller.BaseController;
import com.tbit.uqbike.webmanager.dao.core.*;
import com.tbit.uqbike.webmanager.dao.log.OrderLogDao;
import com.tbit.uqbike.webmanager.util.ServletUtils;
import com.tbit.uqbike.webmanager.util.aliyun.OssFileUtils;
import com.tbit.uqbike.webmanager.util.binary.FormatTransfer;
import com.tbit.uqbike.webmanager.util.binary.ProtocolUtil;
import com.tbit.uqbike.webmanager.util.redis.RedisUtils;
import com.tbit.utils.GeoUtil;
import com.tbit.utils.TestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 语音任务service
 *
 * @author <a href="mailto:liu.haoxiang@tbit.com.cn">BUGXiang</a>
 * @version 1.0
 * @since 2024/3/15
 */
@Service
@Slf4j
public class VoiceTaskServiceImpl implements VoiceTaskService {

    @Resource
    private VoiceTaskDao voiceTaskDao;
    @Resource
    private VoiceConfigDao voiceConfigDao;
    @Resource
    private UpgradeDao upgradeDao;
    @Resource
    private OssConfigDao ossConfigDao;
    @Resource
    private OrderLogDao orderLogDao;
    @Resource
    private MachineDao machineDao;
    @Resource
    private OssDefaultConfig ossDefaultConfig;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private MQProducer mQProducer;
    @Value("${mq.queue.tbitUqbikeVoice}")
    private String queue;
    @Value("${mq.queue.tbitUqbikeRidePush}")
    private String key;

    /**
     * 分页查询语音任务列表
     *
     * @param voiceTaskDTO voiceTaskDTO
     * @return Result<PageResult < VoiceTaskVO>>
     */
    @Override
    public Result<PageResult<VoiceTaskVO>> queryPage(VoiceTaskDTO voiceTaskDTO) {
        if (Objects.nonNull(voiceTaskDTO.getPageNumber()) && Objects.nonNull(voiceTaskDTO.getSize())) {
            PageInfo<VoiceTaskVO> pageInfo = PageHelper.startPage(voiceTaskDTO.getPageNumber(), voiceTaskDTO.getSize()).doSelectPageInfo(() -> voiceTaskDao.queryPage(voiceTaskDTO));
            return Result.success(new PageResult<VoiceTaskVO>(voiceTaskDTO.getPageNumber(), voiceTaskDTO.getSize(), pageInfo.getTotal(), pageInfo.getPages(), pageInfo.getList()));
        } else {
            List<VoiceTaskVO> vos = voiceTaskDao.queryPage(voiceTaskDTO);
            for (VoiceTaskVO voiceTaskVO : vos) {
                List<Point> points = GeoUtil.getPoints(voiceTaskVO.getPlayPoint());
                voiceTaskVO.setCenterPoint(GeoUtil.GetCenterPoint(points));
            }
            return Result.success(new PageResult<>(null, null, null, null, vos));
        }
    }

    /**
     * 新增语音
     *
     * @param voiceTask voiceTask
     * @return Result<Object>
     */
    @Override
    public Result<Object> add(VoiceTask voiceTask, LoginInfo login) {
        QueryWrapper<VoiceTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("taskName", voiceTask.getTaskName());
        VoiceTask task = voiceTaskDao.selectOne(queryWrapper);
        if (Objects.nonNull(task) && Objects.nonNull(task.getVoiceTaskId())) {
            return Result.error("任务名称已存在");
        }
        //校验围栏是否重叠
        QueryWrapper<VoiceTask> wrapper = new QueryWrapper<>();
        wrapper.eq("accountId", voiceTask.getAccountId());
        List<VoiceTask> voiceTasks = voiceTaskDao.selectList(wrapper);
        for (VoiceTask taskVoice : voiceTasks) {
            if (TestUtil.RegionOverlap(voiceTask.getPlayPoint(), taskVoice.getPlayPoint())) {
                return Result.error("围栏重叠，请重新绘制");
            }
        }
        voiceTask.setCreateTime(new Date())
                .setCreateUser(login.getId())
                .setDelFlag(0);
        voiceTaskDao.insert(voiceTask);
        return Result.success();
    }

    /**
     * 编辑语音任务
     *
     * @param dto voiceTask
     * @return Result<Object>
     */
    @Override
    public Result<Object> update(TaskDTO dto) {
        LoginInfo login = BaseController.getLogin(ServletUtils.getRequest());
        QueryWrapper<VoiceTask> voiceQueryWrapper = new QueryWrapper<>();
        voiceQueryWrapper.eq("taskName", dto.getTaskName()).ne("voiceTaskId", dto.getVoiceTaskId());
        VoiceTask task = voiceTaskDao.selectOne(voiceQueryWrapper);
        if (Objects.nonNull(task)) {
            return Result.error("语音任务名称重复");
        }
        dto.setUpdateTime(new Date());
        voiceTaskDao.updateById(dto);
        // 全量升级
        updateNow(dto.getBrandId(), dto.getAccountId(), login);
        timer(dto.getBrandId(), dto.getAccountId(), login);
        return Result.success();
    }


    /**
     * 立即更新配置到设备
     *
     * @return Result
     */
    @Override
    public Result<Object> updateNow(Integer brandId, Integer accountId, LoginInfo login) {
        //获取所有的语音任务和需要升级车辆
        List<WriteConfigVoice> writeConfigVoice = voiceTaskDao.getConfig(accountId);
        List<UpgradeVO> machineList = upgradeDao.selectMachineByAccountId(accountId);
        //写配置文件
        String fileUrl = WriteConfig(writeConfigVoice, brandId, accountId);
        if (Objects.isNull(fileUrl)) {
            return Result.error("生成配置文件失败");
        }
        //需要升级的设备编号缓存成hash key-> machineNo value-> uuid
        Joiner joiner = Joiner.on(":");
        String[] words = {brandId.toString(), accountId.toString(), DateUtil.today()};
        String redisSn = joiner.join(words);
        for (UpgradeVO upgradeVO : machineList) {
            redisUtils.hPut(redisSn, upgradeVO.getMachineNo(), UUID.randomUUID().toString());
            redisUtils.expire(redisSn, 60, TimeUnit.SECONDS);
        }
        //设置参数到网关
        TerMsg terMsg = new TerMsg();
        terMsg.setRedisSn(redisSn);
        terMsg.setControlType(ControlTypeConstant.CONTROL_TYPE_SET);
        terMsg.setParamName("CONFIGFILEURLDOWNLOAD");
        terMsg.setParamValue(writeConfigVoice.get(0).getVersion() + "-" + fileUrl);
        terMsg.setAsync(true);
        /**发送到MQ*/
        log.info(String.format("升级语音发送指令，route_key=%s,routeKey=%s,MQ_MSG_ID_CONTROL_TER=%s,termsg=%s", "ter_res_info", RabbitMQData.routeKey, GatewayProtocol.MQ_MSG_ID_CONTROL_TER, JSON.toJSONString(terMsg)));
        mQProducer.sendDataToQueue(key, queue, GatewayProtocol.MQ_MSG_ID_CONTROL_TER, terMsg);
        List<OrderLog> orderLogs = new ArrayList<>();
        for (UpgradeVO upgradeVO : machineList) {
            OrderLog orderLog = new OrderLog();
            orderLog.setOptype(login.type);
            orderLog.setOpid(login.id);
            orderLog.setMachineid(upgradeVO.getMachineId());
            orderLog.setMachineno(upgradeVO.getMachineNo());
            orderLog.setUsercode(upgradeVO.getUserCode());
            orderLog.setOptime(new Date());
            orderLog.setChanneltype(OrderLogConstant.CHANNEL_TYPE_GPRS);
            orderLog.setOrdersource(OrderLogConstant.ORDER_SOURCE_WEB_MANAGER);
            orderLog.setOrderserno(null);
            orderLogs.add(orderLog);
        }
        orderLogDao.batchAdd(orderLogs);
        //设置需要升级的车辆状态为升级中
        QueryWrapper<UpgradeMachine> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("accountId",accountId);
        UpgradeMachine upgradeMachine = new UpgradeMachine();
        upgradeMachine.setUpgradeType("5");
        upgradeDao.update(upgradeMachine,queryWrapper);
        return Result.success();
    }

    /**
     * 更新单个设备
     *
     * @param brandId   品牌id
     * @param accountId 区域id
     * @param machineNo 设备编号
     * @return result
     */
    @Override
    public Result<Object> updateOne(Integer brandId, Integer accountId, String machineNo, LoginInfo login) {
        //获取所有的语音任务和需要升级车辆
        List<WriteConfigVoice> writeConfigVoice = voiceTaskDao.getConfig(accountId);
        //写配置文件
        String fileUrl = WriteConfig(writeConfigVoice, brandId, accountId);
        if (Objects.isNull(fileUrl)) {
            return Result.error("生成配置文件失败");
        }
        //设置参数到网关
        TerMsg terMsg = new TerMsg();
        terMsg.setSn(machineNo);
        terMsg.setSerNO(UUID.randomUUID().toString());
        terMsg.setControlType(ControlTypeConstant.CONTROL_TYPE_SET);
        terMsg.setParamName("CONFIGFILEURLDOWNLOAD");
        terMsg.setParamValue(writeConfigVoice.get(0).getVersion() + "-" + fileUrl);
        terMsg.setAsync(false);
        /**发送到MQ*/
        log.info(String.format("升级语音发送指令，route_key=%s,routeKey=%s,MQ_MSG_ID_CONTROL_TER=%s,termsg=%s", "ter_res_info", RabbitMQData.routeKey, GatewayProtocol.MQ_MSG_ID_CONTROL_TER, JSON.toJSONString(terMsg)));
        mQProducer.sendDataToQueue(RabbitMQData.routeKey, queue, GatewayProtocol.MQ_MSG_ID_CONTROL_TER, terMsg);
        Machine machine = machineDao.getByMachineNO(machineNo);
        OrderLog orderLog = new OrderLog();
        if (Objects.nonNull(machine)) {
            orderLog.setMachineid(machine.getMachineId());
            orderLog.setUsercode(machine.getUserCode());
        }
        orderLog.setOptype(login.type);
        orderLog.setOpid(login.id);
        orderLog.setMachineno(machineNo);
        orderLog.setOptime(new Date());
        orderLog.setChanneltype(OrderLogConstant.CHANNEL_TYPE_GPRS);
        orderLog.setOrdersource(OrderLogConstant.ORDER_SOURCE_WEB_MANAGER);
        orderLogDao.insert(orderLog);
        //修改单辆车的状态为升级中
        upgradeDao.updateStatus(machineNo,5);
        return Result.success();
    }


    /**
     * 30分钟后生产配置文件
     *
     * @param brandId
     * @param accountId
     * @param login
     */
    public void timer(Integer brandId, Integer accountId, LoginInfo login) {
        Timer timer = new Timer();
        timer.cancel();
        Timer timerNew = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                updateNow(brandId, accountId, login);
            }
        };
        timerNew.schedule(timerTask, 10 * 60 * 1000);
    }

    /**
     * 生成配置文件
     *
     * @param voiceTasks 语音任务
     */
    private String WriteConfig(List<WriteConfigVoice> voiceTasks, Integer brandId, Integer accountId) {

        //版本号
        long configVersion = System.currentTimeMillis() / 1000;
        byte[] version = FormatTransfer.toHH((int)configVersion);
        //上传电子围栏资源文件
        String url = uploadGeoConfig(voiceTasks, brandId, accountId, version);
        if (Objects.isNull(url)) {
            throw new BaseException("围栏资源url为空");
        }
        //全局配置文件下标
        int checkIndex;
        int lengthIndex;
        int firstLengthIndex;
        int secondLengthIndex;
        int thirdIndex;
        List<byte[]> voiceConfig = new ArrayList<>();
        try {
            voiceConfig.add("TBITCFG".getBytes("UTF-8"));
            voiceConfig.add(version);
            //文件类型
            byte[] fileType = {0x31};
            voiceConfig.add(fileType);
            //文件校验 short
            voiceConfig.add(FormatTransfer.toHH((short) 10));
            checkIndex = voiceConfig.size() - 1;
            //文件长度
            voiceConfig.add(FormatTransfer.toHH(10));
            lengthIndex = voiceConfig.size() - 1;
            //配置文件个数 配置文件个数*N
            voiceConfig.add(FormatTransfer.toHH((short) voiceConfig.size() * 3));
            //配置文件内容
            voiceConfig.add(new byte[]{2});
            //首个配置内容长度下标
            voiceConfig.add(FormatTransfer.toHH((short) 10));
            firstLengthIndex = voiceConfig.size() - 1;
            voiceConfig.add(version);

            for (WriteConfigVoice voiceTask : voiceTasks) {
                //事件任务资源id
                voiceConfig.add(FormatTransfer.toHH((short) voiceTask.getVoiceTaskId().intValue()));
                //todo 事件任务资源id对应的资源偏移位
                voiceConfig.add(FormatTransfer.toHH((short) 2));
                //任务是否有效
                voiceConfig.add(new byte[]{1});
                //触发类型 0：单次；1：多次；2：永久；3：周期
                voiceConfig.add(new byte[]{3});
                //次数 此字段触发类型为单次和多次时有效
                voiceConfig.add(new byte[]{0});
                //周期
                voiceConfig.add(new byte[]{1});
                voiceConfig.add(new byte[]{1});
                voiceConfig.add(new byte[]{1});
                voiceConfig.add(new byte[]{1});
                voiceConfig.add(new byte[]{1});
                voiceConfig.add(new byte[]{1});
                voiceConfig.add(new byte[]{1});
                //时
                voiceConfig.add(new byte[]{1});
                //执行时间段个数
                voiceConfig.add(FormatTransfer.toHH((short) 360));
                voiceConfig.add(FormatTransfer.toHH((short) 1380));
                //执行动作个数
                voiceConfig.add(FormatTransfer.toHH((short) 1));
                //执行动作场景 驶入围栏
                voiceConfig.add(new byte[]{4});
                //执行动作拓展场景
                voiceConfig.add(new byte[]{2});
                voiceConfig.add(new byte[]{0});
                //拓展内容长度
                voiceConfig.add(new byte[]{2});
                //拓展内容
                voiceConfig.add(FormatTransfer.toHH((short) voiceTask.getRange().intValue()));
                //执行动作配置 0-> 播放语音
                voiceConfig.add(new byte[]{0});
                //动作内容 语音id
                voiceConfig.add(FormatTransfer.toHH((short) voiceTask.getVoiceId().intValue()));
                //todo 电子围栏配置 围栏id 偏移位
                voiceConfig.add(FormatTransfer.toHH((short) 1));
                voiceConfig.add(FormatTransfer.toHH((short) 2));
                //围栏资源url的长度
                voiceConfig.add(FormatTransfer.toHH((short) url.length()));
                //围栏资源url
                voiceConfig.add(url.getBytes());
                //关联的事件id个数
                voiceConfig.add(FormatTransfer.toHH((short) 1));
                //关联的事件id
                voiceConfig.add(FormatTransfer.toHH((short) voiceTask.getVoiceTaskId().intValue()));
                // 语音资源id
                voiceConfig.add(FormatTransfer.toHH((short) voiceTask.getVoiceId().intValue()));
                //todo 语音语音id对应的偏移位
                voiceConfig.add(FormatTransfer.toHH((short) 1));
                //语音资源的url长度
                voiceConfig.add(FormatTransfer.toHH((short) voiceTask.getFileUrl().length()));
                //语音资源url
                voiceConfig.add(voiceTask.getFileUrl().getBytes());
            }
            //文件校验
            byte[] bytes = convertListToByteArray(voiceConfig);
            short fileCheck = ProtocolUtil.GetCrcByteArray(bytes, 0, bytes.length);
            voiceConfig.set(checkIndex, FormatTransfer.toHH(fileCheck));
            //todo 三个配置文件的资源偏移位

            //上传到oss
            //语音文件上传到阿里云
            OssConfig ossConfig = Optional.ofNullable(ossConfigDao.getByAccountId(brandId))
                    .orElseGet(() -> new OssConfig(ossDefaultConfig.getEndpoint(),
                            ossDefaultConfig.getAccessKeyId(),
                            ossDefaultConfig.getAccessKeySecret(), "gx-voice"));
            String fileName = "voice.bin";
            FileOutputStream fileOutputStream = new FileOutputStream(fileName);
            String pathName = String.format("%s/%s%s", brandId, UUID.randomUUID(), fileName);
            fileOutputStream.write(bytes);
            fileOutputStream.close();
            log.info("文件生成完毕，开始上传oss");
            File file = new File(fileName);
            String fileUrl = OssFileUtils.uploadByAliYun(new FileInputStream(file), ossConfig, pathName, "bin", 1);
            //配置文件入库
            VoiceConfig config = new VoiceConfig();
            config.setAccountId(accountId);
            config.setUpdateTime(new Date());
            config.setVersion(String.valueOf(configVersion));
            config.setParam(fileUrl);
            QueryWrapper<VoiceConfig> voiceConfigQueryWrapper = new QueryWrapper<>();
            voiceConfigQueryWrapper.eq("accountId",accountId);
            voiceConfigDao.delete(voiceConfigQueryWrapper);
            voiceConfigDao.insert(config);
            return fileUrl;
        } catch (Exception e) {
            log.error("生成配置文件报错:{}", e.getMessage(), e);
        }
        return null;
    }

    /**
     * 上传电子围栏资源文件
     *
     * @param voiceTasks voiceTasks
     * @return ossUrl
     */
    private String uploadGeoConfig(List<WriteConfigVoice> voiceTasks, Integer brandId, Integer accountId, byte[] version) {
        List<byte[]> configBytes = new ArrayList<>();
        int fileCheckIndex;
        int fileLength;
        String fileName;
        try {
            configBytes.add("TBITRES".getBytes("UTF-8"));
            //版本号
            configBytes.add(version);
            //文件类型
            byte[] fileType = {0x50};
            configBytes.add(fileType);
            //文件校验 short
            short check = 10;
            configBytes.add(FormatTransfer.toHH(check));
            fileCheckIndex = configBytes.size() - 1;
            //文件长度 int
            configBytes.add(FormatTransfer.toHH(10));
            fileLength = configBytes.size() - 1;
            // 围栏id short
            short geoId = 10;
            configBytes.add(FormatTransfer.toHH(geoId));
            //围栏长度
            configBytes.add(FormatTransfer.toHH(voiceTasks.size()));
            for (WriteConfigVoice voiceTask : voiceTasks) {
                String[] split = voiceTask.getPlayPoint().split(";");
                //围栏个数 short
                short geoCount = (short) split.length;
                configBytes.add(FormatTransfer.toHH(geoCount));
                for (String s : split) {
                    String[] split1 = s.split(",");
                    configBytes.add(FormatTransfer.toHH(Float.parseFloat(split1[0])));
                    configBytes.add(FormatTransfer.toHH(Float.parseFloat(split1[1])));
                }
            }
            byte[] bytes = convertListToByteArray(configBytes);
            short fileCheck = ProtocolUtil.GetCrcByteArray(bytes, 0, bytes.length);
            configBytes.set(fileCheckIndex, FormatTransfer.toHH(fileCheck));
            //计算文件长度
            configBytes.set(fileLength, FormatTransfer.toHH(bytes.length));
            fileName = "/geo.bin";
            FileOutputStream fileOutputStream = new FileOutputStream(fileName);
            //list转byte数组
            byte[] writeBytes = convertListToByteArray(configBytes);
            fileOutputStream.write(writeBytes);
            fileOutputStream.close();
            log.info("文件生成完毕，开始上传oss");
            //语音文件上传到阿里云
            OssConfig ossConfig = Optional.ofNullable(ossConfigDao.getByAccountId(brandId))
                    .orElseGet(() -> new OssConfig(ossDefaultConfig.getEndpoint(),
                            ossDefaultConfig.getAccessKeyId(),
                            ossDefaultConfig.getAccessKeySecret(), "gx-voice"));
            String pathName = String.format("%s/%s%s", brandId, UUID.randomUUID(), fileName);
            File file = new File(fileName);
            return OssFileUtils.uploadByAliYun(new FileInputStream(file), ossConfig, pathName, "bin", 1);
        } catch (Exception e) {
            log.error("生成围栏配置文件报错:{}", e.getMessage(), e);
        }
        return null;
    }


    /**
     * 将 List<byte[]> 转换为单个 byte 数组
     */
    public byte[] convertListToByteArray(List<byte[]> list) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            for (byte[] bytes : list) {
                byteArrayOutputStream.write(bytes);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
}
